﻿/*
 * Microsoft .NET  2.0+ version of Sphinx searchd client (Sphinx API)
 * 
 * Copyright (c) 2009, Shaun Berryman
 * All rights reserved
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License. You should have
 * received a copy of the GPL license along with this program; if you
 * did not, you can find it at http://www.gnu.org/
 */

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Runtime.CompilerServices;

namespace SphinxSearchApi.Extension
{
    internal static class IO
    {
        #region Readers

        public static string eReadUTF8(this BinaryReader reader)
        {
            int length = reader.eReadInt32();
            if (length > 0)
            {
                byte[] data = reader.ReadBytes(length);
                return Encoding.UTF8.GetString(data);
            }

            // Failed
            return string.Empty;
        }

        public static short eReadInt16(this BinaryReader br)
        {
            byte[] idata = br.ReadBytes(2);
            return BitConverter.ToInt16(idata.Reverse(), 0);
        }

        public static int eReadInt32(this BinaryReader br)
        {
            byte[] idata = br.ReadBytes(4);
            return BitConverter.ToInt32(idata.Reverse(), 0);
        }

        public static uint eReadUInt32(this BinaryReader br)
        {
            byte[] idata = br.ReadBytes(4);

            return BitConverter.ToUInt32(idata.Reverse(), 0);
        }

        public static UInt64 eReadUInt64(this BinaryReader br)
        {
            byte[] idata = br.ReadBytes(8);
            return BitConverter.ToUInt64(idata.Reverse(), 0);
        }

        public static Int64 eReadInt64(this BinaryReader br)
        {
            byte[] idata = br.ReadBytes(4);
            return 
                Int32BigIntToBits(
                    BitConverter.ToInt32(
                        idata.Reverse(), 
                        0
                        )
                    );
        }

        public static float eReadFloat(this BinaryReader br)
        {
            byte[] idata = br.ReadBytes(4);
            return 
                Int32FloatToBits(
                    BitConverter.ToInt32(
                        idata.Reverse(), 
                        0
                        )
                    );
        }

        public static double eReadDouble(this BinaryReader br)
        {
            byte[] idata = br.ReadBytes(8);
            return BitConverter.ToDouble(idata.Reverse(), 0);
        }

        #endregion

        #region Writers (BinaryWriter)

        public static void WriteToStream(this BinaryWriter writer, short data)
        {
            byte[] d = BitConverter.GetBytes(data);
            writer.Write(d.Reverse());
        }

        public static void WriteToStream(this BinaryWriter writer, int data)
        {
            byte[] d = BitConverter.GetBytes(data);
            writer.Write(d.Reverse());
        }

        public static void WriteToStream(this BinaryWriter writer, long data)
        {
            byte[] d = BitConverter.GetBytes(data);
            writer.Write(d.Reverse());
        }

        public static void WriteToStream(this BinaryWriter writer, float data)
        {
            int intBits = FloatToInt32Bits(data);
            byte[] d = BitConverter.GetBytes(intBits);
            writer.Write(d.Reverse());
        }

        public static void WriteToStream(this BinaryWriter writer, UInt64 data)
        {
            int intBits = UInt64ToInt32Bits(data);
            byte[] d = BitConverter.GetBytes(intBits);
            writer.Write(d.Reverse());
        }

        public static void WriteToStream(this BinaryWriter writer, byte[] data)
        {
            writer.WriteToStream(data, false);
        }

        public static void WriteToStream(this BinaryWriter writer, byte[] data, bool writeLength)
        {
            if (writeLength)
                writer.WriteToStream(data.Length);

            writer.Write(data);
        }

        /// <summary>
        /// Writes the string data to the stream. Will conver to UTF8 AND
        /// automatically add the string length.
        /// </summary>
        /// <param name="writer"></param>
        /// <param name="data"></param>
        public static void WriteToStream(this BinaryWriter writer, string data)
        {
            if (string.IsNullOrEmpty(data))
                writer.WriteToStream((int)0);
            else
            {
                byte[] d = Encoding.UTF8.GetBytes(data);
                writer.WriteToStream(d.Length);
                writer.Write(d);
            }
        }

        public static void WriteToStream(this BinaryWriter writer, CommandEnum command)
        {
            // Write the command
            writer.WriteToStream((short)command);

            // Write the version
            switch (command)
            {
                case CommandEnum.Search: writer.WriteToStream((short)VersionEnum.Search); break;
                case CommandEnum.Excerpt: writer.WriteToStream((short)VersionEnum.Excerpt); break;
                case CommandEnum.Update: writer.WriteToStream((short)VersionEnum.Update); break;
                case CommandEnum.Keywords: writer.WriteToStream((short)VersionEnum.Keywords); break;
                case CommandEnum.Persist: writer.WriteToStream((short)VersionEnum.Persist); break;
                case CommandEnum.Status: writer.WriteToStream((short)VersionEnum.Status); break;
                case CommandEnum.Query: writer.WriteToStream((short)VersionEnum.Query); break;
                case CommandEnum.FlushAttributes: writer.WriteToStream((short)VersionEnum.FlushAttributes); break;
            }
        }

        #endregion 

        #region Writers (MemoryStream)

        public static void WriteToStream(this MemoryStream writer, short data)
        {
            byte[] d = BitConverter.GetBytes(data);
            writer.Write(d.Reverse(), 0, d.Length);
        }

        public static void WriteToStream(this MemoryStream writer, int data)
        {
            byte[] d = BitConverter.GetBytes(data);
            writer.Write(d.Reverse(), 0, d.Length);
        }

        public static void WriteToStream(this MemoryStream writer, long data)
        {
            byte[] d = BitConverter.GetBytes(data);
            writer.Write(d.Reverse(), 0, d.Length);
        }

        public static void WriteToStream(this MemoryStream writer, float data)
        {
            int intBits = FloatToInt32Bits(data);
            byte[] d = BitConverter.GetBytes(intBits);
            writer.Write(d.Reverse(), 0, d.Length);
        }

        public static void WriteToStream(this MemoryStream writer, UInt64 data)
        {
            int intBits = UInt64ToInt32Bits(data);
            byte[] d = BitConverter.GetBytes(intBits);
            writer.Write(d.Reverse(), 0, d.Length);
        }

        public static void WriteToStream(this MemoryStream writer, byte[] data)
        {
            writer.WriteToStream(data, false);
        }

        public static void WriteToStream(this MemoryStream writer, byte[] data, bool writeLength)
        {
            if (writeLength)
                writer.WriteToStream(data.Length);

            writer.Write(data, 0, data.Length);
        }

        /// <summary>
        /// Writes the string data to the stream. Will conver to UTF8 AND
        /// automatically add the string length.
        /// </summary>
        /// <param name="writer"></param>
        /// <param name="data"></param>
        public static void WriteToStream(this MemoryStream writer, string data)
        {
            if (string.IsNullOrEmpty(data))
                writer.WriteToStream((int)0);
            else
            {
                byte[] d = Encoding.UTF8.GetBytes(data);
                writer.WriteToStream(d.Length);
                writer.Write(d, 0, d.Length);
            }
        }

        public static void WriteToStream(this MemoryStream writer, CommandEnum command)
        {
            // Write the command
            writer.WriteToStream((short)command);

            // Write the version
            switch (command)
            {
                case CommandEnum.Search: writer.WriteToStream((short)VersionEnum.Search); break;
                case CommandEnum.Excerpt: writer.WriteToStream((short)VersionEnum.Excerpt); break;
                case CommandEnum.Update: writer.WriteToStream((short)VersionEnum.Update); break;
                case CommandEnum.Keywords: writer.WriteToStream((short)VersionEnum.Keywords); break;
                case CommandEnum.Persist: writer.WriteToStream((short)VersionEnum.Persist); break;
                case CommandEnum.Status: writer.WriteToStream((short)VersionEnum.Status); break;
                case CommandEnum.Query: writer.WriteToStream((short)VersionEnum.Query); break;
                case CommandEnum.FlushAttributes: writer.WriteToStream((short)VersionEnum.FlushAttributes); break;
            }
        }

        #endregion 

        #region Private helpers

        public static byte[] Reverse(this byte[] data)
        {
            int len = data.Length;
            for (long i = 0; i < len / 2; ++i)
            {
                byte t;
                t = data[i];
                data[i] = data[len - i - 1];
                data[len - i - 1] = t;
            }

            return data;
        }
        private static unsafe int FloatToInt32Bits(float value)
        {
            return *(((int*)&value));
        }
        private static unsafe float Int32FloatToBits(int value)
        {
            return *(((float*)&value));
        }
        private static unsafe Int64 Int32BigIntToBits(int value)
        {
            return *(((Int64*)&value));
        }
        private static unsafe int UInt64ToInt32Bits(UInt64 value)
        {
            return *(((int*)&value));
        }

        #endregion
    }
}
